/* @flow */

import config from "../config";
import Watcher from "../observer/watcher";
import Dep, {
  pushTarget,
  popTarget
} from "../observer/dep";
import {
  isUpdatingChildComponent
} from "./lifecycle";

import {
  set,
  del,
  observe,
  defineReactive,
  toggleObserving,
} from "../observer/index";

import {
  warn,
  bind,
  noop,
  hasOwn,
  hyphenate,
  isReserved,
  handleError,
  nativeWatch,
  validateProp,
  isPlainObject,
  isServerRendering,
  isReservedAttribute,
} from "../util/index";

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop,
};

export function proxy(target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter() {
    return this[sourceKey][key];
  };
  sharedPropertyDefinition.set = function proxySetter(val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

export function initState(vm: Component) {
  debugger;
  /**
   * 给实例新增一个_watchers属性，用来存储当前实例中所有的watcher实例，
   * 无论是使用vm.$watch注册的watcher实例还是使用watch选项注册的watcher实例，都会被保存到该属性中。
   * vue在1版本，是每个数据对应一个dep,然后dep管理这个数据用到的watcher,数据越多造成内存开销大。在2.0版本，Vue不再对所有数据都进行侦测，
   * 而是将侦测粒度提高到了组件层面，对每个组件进行侦测，所以在每个组件上新增了vm._watchers属性，用来存放这个组件内用到的所有状态的依赖，
   * 当其中一个状态发生变化时，就会通知到组件，然后由组件内部使用虚拟DOM进行数据比对，从而降低内存开销，提高性能。
   */
  vm._watchers = [];
  const opts = vm.$options;
  /**
   *props: ['title']  统一格式化为{"title": {"type": null}}
   *props:{
        name:true 一定会校验成功
        propA: Number,   统一格式化为{"propA": {"type": ƒ Number()}}
        propB: [String, Number], 统一格式化为{"propB": {"type": [ƒ Number() , ƒ String()]}}
        propC: {
            type: String,
            required: true
        },
         propD: {
            type: Number,
            //default: 100
            default: function () {
                return { message: 'hello' }
            },
            自定义验证函数
            validator: function (value) {
                // 这个值必须匹配下列字符串中的一个
                return ['success', 'warning', 'danger'].indexOf(value) !== -1
            }


        },
   }
   *
   */
  if (opts.props) initProps(vm, opts.props);
  /**
   * 判断方法名对应的值必须是函数，名字不能和prop重名 不能以$, _开头
   * 返回函数bind执行,如果对应的值不是函数则返回一个空函数
   * 把以方法名为key，值为value绑定到vue实例上
   */
  if (opts.methods) initMethods(vm, opts.methods);

  /**
   * data不能与methods和props重名，增加代理_data
   */
  if (opts.data) {
    initData(vm);
  } else {
    //如果没有datab把data设置为空对象并将其转化为响应式
    observe((vm._data = {}), true /* asRootData */ );
  }

  /**
   *computed选项中的属性值可以是一个函数，那么该函数默认为取值器getter，用于仅读取数据；还可以是一个对象，对象里面有取值器getter和存值器setter，用于读取和设置数据。
    仅读取
    aDouble: function () {
      return this.a * 2
    },

    读取和设置
    aPlus: {
      get: function () {
        return this.a + 1
      },
      set: function (v) {
        this.a = v - 1
      }
    }
   */

  if (opts.computed) initComputed(vm, opts.computed);
  if (opts.watch && opts.watch !== nativeWatch) {
    /**
     *watch{
         a: function (val, oldVal) {
            console.log('new: %s, old: %s', val, oldVal)
         },
         // methods选项中的方法名
        b: 'someMethod',
         // 深度侦听，该回调会在任何被侦听的对象的 property 改变时被调用，不论其被嵌套多深
        c: {
            handler: function (val, oldVal) { }
            deep: true
        }
        // 该回调将会在侦听开始之后被立即调用
        d: {
            handler: 'someMethod',
            immediate: true
        },
        // 调用多个回调
        e: [
            'handle1',
            function handle2 (val, oldVal) {  },
            {
                handler: function handle3 (val, oldVal) {},
            }
        ],
        // 侦听表达式
        'e.f': function (val, oldVal) {}
     *
     *内部调用vm.$watcher
     *
     *
     *
     */
    initWatch(vm, opts.watch);
  }
}

function initProps(vm: Component, propsOptions: Object) {
  debugger;
  //父组件传入的真实props数据。
  const propsData = vm.$options.propsData || {};
  //vm._props的指针，所有设置到props变量中的属性都会保存到vm._props中。
  const props = (vm._props = {});
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  //vm.$options._propKeys的指针，缓存props对象中的key
  const keys = (vm.$options._propKeys = []);
  //当前组件是否为根组件。
  const isRoot = !vm.$parent;
  // root instance props should be converted
  //判断当前组件是否为根组件，如果不是，那么不需要将props数组转换为响应式的
  if (!isRoot) {
    toggleObserving(false);
  }
  for (const key in propsOptions) {
    keys.push(key);
    //校验并得到传入的值value
    const value = validateProp(key, propsOptions, propsData, vm);
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== "production") {
      const hyphenatedKey = hyphenate(key);
      if (
        isReservedAttribute(hyphenatedKey) ||
        config.isReservedAttr(hyphenatedKey)
      ) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        );
      }
      defineReactive(props, key, value, () => {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${key}"`,
            vm
          );
        }
      });
    } else {
      //将键和值通过defineReactive函数添加到props（即vm._props）
      defineReactive(props, key, value);
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    //判断key是否在vm上，如果不在做代理，当使用vm[key]访问数据时，其实访问的是vm._props[key]
    if (!(key in vm)) {
      proxy(vm, `_props`, key);
    }
  }
  toggleObserving(true);
}

function initData(vm: Component) {
  debugger;
  let data = vm.$options.data;
  data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
  //判断data是否是一个对象
  if (!isPlainObject(data)) {
    data = {};
    process.env.NODE_ENV !== "production" &&
      warn(
        "data functions should return an object:\n" +
        "https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function",
        vm
      );
  }
  // proxy data on instance
  const keys = Object.keys(data);
  const props = vm.$options.props;
  const methods = vm.$options.methods;
  let i = keys.length;
  while (i--) {
    const key = keys[i];
    if (process.env.NODE_ENV !== "production") {
      //判断data里面的数据key不能与methods重名
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        );
      }
    }
    //判断data里面的数据key不能与props重名
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== "production" &&
        warn(
          `The data property "${key}" is already declared as a prop. ` +
          `Use prop default value instead.`,
          vm
        );
    } else if (!isReserved(key)) {
      //data不是以$或者_开头
      //// 数据代理，用户可直接通过vm实例返回data数据
      proxy(vm, `_data`, key);
    }
  }
  // observe data调用observe函数将data中的数据转化成响应式，
  observe(data, true /* asRootData */ );
}

export function getData(data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget();
  try {
    return data.call(vm, vm);
  } catch (e) {
    handleError(e, vm, `data()`);
    return {};
  } finally {
    popTarget();
  }
}

const computedWatcherOptions = {
  lazy: true
};

function initComputed(vm: Component, computed: Object) {
  // $flow-disable-line
  //定义一个watchers赋值为空对象并将其作为指针指向vm._computedWatchers
  const watchers = (vm._computedWatchers = Object.create(null));
  // computed properties are just getters during SSR
  const isSSR = isServerRendering();

  //遍历每一个computed属性
  for (const key in computed) {
    const userDef = computed[key]; //computed的值
    //判断computed的值是否是函数，如果是函数直接赋值给getter，否则取出其中的get
    const getter = typeof userDef === "function" ? userDef : userDef.get;
    if (process.env.NODE_ENV !== "production" && getter == null) {
      //如果getter为空，报错
      warn(`Getter is missing for computed property "${key}".`, vm);
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      //为每一个computed创建一个Watcher
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      );
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    //判断当前循环到的的属性名是否存在于当前实例vm上，如果存在，则在非生产环境下抛出警告；如果不存在，则调用defineComputed函数为实例vm上设置计算属性。
    if (!(key in vm)) {
      defineComputed(vm, key, userDef);
    } else if (process.env.NODE_ENV !== "production") {
      //下面是判断computed不能与props和data重名
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm);
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(
          `The computed property "${key}" is already defined as a prop.`,
          vm
        );
      }
    }
  }
}
//作用是为target上定义一个属性key，并且属性key的getter和setter根据userDef的值来设置。
export function defineComputed(
  target: any,
  key: string,
  userDef: Object | Function
) {
  //shouldCache标识计算属性是否应该有缓存,该变量的值是当前环境是否为非服务端渲染环境，非服务端环境缓存
  const shouldCache = !isServerRendering();
  if (typeof userDef === "function") {
    sharedPropertyDefinition.get = shouldCache ?
      createComputedGetter(key) //如果缓存，没有直接使用userDef作为getter，而是createComputedGetter函数返回一个getter，为了创建具有缓存功能的getter
      :
      createGetterInvoker(userDef);
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get ?
      shouldCache && userDef.cache !== false ?
      createComputedGetter(key) :
      createGetterInvoker(userDef.get) :
      noop;
    sharedPropertyDefinition.set = userDef.set || noop;
  }
  //如果用户没有设置setter的话，那么就给setter一个默认函数，这是为了防止用户在没有设置setter的情况下修改计算属性，从而为其抛出警告
  if (
    process.env.NODE_ENV !== "production" &&
    sharedPropertyDefinition.set === noop
  ) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      );
    };
  }
  //将计算属性绑定到实例vm上
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

function createComputedGetter(key) {
  //sharedPropertyDefinition.getj就是computedGetter
  return function computedGetter() {
    //获取this._computedWatchers上的key对应的watcher
    const watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      //如果watcher存在，则调用watcher实例上的depend方法和evaluate方法，
      //只有dirty为true才会重新求值
      if (watcher.dirty) {
        watcher.evaluate();
      }
      if (Dep.target) {
        watcher.depend();
      }
      return watcher.value;
    }
  };
}

function createGetterInvoker(fn) {
  return function computedGetter() {
    return fn.call(this, this);
  };
}

function initMethods(vm: Component, methods: Object) {
  const props = vm.$options.props;
  for (const key in methods) {
    if (process.env.NODE_ENV !== "production") {
      //判断必须是个函数
      if (typeof methods[key] !== "function") {
        warn(
          `Method "${key}" has type "${typeof methods[
            key
          ]}" in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        );
      }
      //方法名字不能和prop重名
      if (props && hasOwn(props, key)) {
        warn(`Method "${key}" has already been defined as a prop.`, vm);
      }
      //方法名不能以$, _开头
      if (key in vm && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        );
      }
    }
    //noop是一个空函数
    vm[key] =
      typeof methods[key] !== "function" ? noop : bind(methods[key], vm);
  }
}

function initWatch(vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key];
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i]);
      }
    } else {
      createWatcher(vm, key, handler);
    }
  }
}

function createWatcher(
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options ? : Object
) {
  if (isPlainObject(handler)) {
    options = handler;
    handler = handler.handler;
  }
  if (typeof handler === "string") {
    handler = vm[handler];
  }
  return vm.$watch(expOrFn, handler, options);
}

export function stateMixin(Vue: Class < Component > ) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  const dataDef = {};
  dataDef.get = function () {
    return this._data;
  };
  const propsDef = {};
  propsDef.get = function () {
    return this._props;
  };
  if (process.env.NODE_ENV !== "production") {
    dataDef.set = function () {
      warn(
        "Avoid replacing instance root $data. " +
        "Use nested data properties instead.",
        this
      );
    };
    propsDef.set = function () {
      warn(`$props is readonly.`, this);
    };
  }
  Object.defineProperty(Vue.prototype, "$data", dataDef);
  Object.defineProperty(Vue.prototype, "$props", propsDef);

  Vue.prototype.$set = set;
  Vue.prototype.$delete = del;

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options ? : Object
  ): Function {
    const vm: Component = this;
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options);
    }
    options = options || {};
    options.user = true;
    const watcher = new Watcher(vm, expOrFn, cb, options);
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value);
      } catch (error) {
        handleError(
          error,
          vm,
          `callback for immediate watcher "${watcher.expression}"`
        );
      }
    }
    return function unwatchFn() {
      watcher.teardown();
    };
  };
}
